デットロック と Rust
from 項目17:状態共有並列実行には気を付けよう
Rust でも、ロックの逆転 があると デッドロック が起こりうる
一方では 状態 A → B の順でロックを取得するが、もう一方では状態 B → A の順で取得する
e.g. 複数のプレイヤーがいるゲームサーバの簡略したもの
code:rs
struct GameServer {
players: Mutex<HashMap<String, Player>>,
games: Mutex<HashMap<GameId, Game>>,
}
impl GameServer {
fn add_and_join(&self, username: &str, info: Player) -> Option<GameId> {
let mut players = self.players.lock().unwrap();
players.insert(username.to_owned(), info);
let mut games = self.games.lock().unwrap();
for (id, game) in games.iter_mut() {
if game.add_player(username) {
return Some(id.clone());
}
}
None
}
fn ban_player(&self, username: &str) {
let mut games = self.games.lock().unwrap();
games
.iter_mut()
.filter(|(_id, g)| g.has_player(username) )
.for_each(|(_id, g)| g.remove_player(username) );
let mut players = self.players.lock().unwrap();
players.remove(username);
}
}
2 つのスレッドが add_and_join と ban_player を呼び出した場合、実行順序が以下のようになりデッドロックが起こる可能性がある
table:_
スレッド① スレッド②
add_and_join` に入り、即座に players のロックを取得する
ban_player に入り、即座に games のロックを取得する
games のロックの取得を試みるが、
スレッド ②が持っているのでブロックされる
players ロックの取得を試みるが、
スレッド① が持っているのでブロックされる
解決策①: ロックのスコープを短くして、両方のロックを保持しないようにする
e.g.
code:rs
fn add_and_join(&self, username: &str, info: Player) -> Option<GameId> {
{
let mut players = self.players.lock().unwrap();
players.insert(username.to_owned(), info);
}
{
let mut games = self.games.lock().unwrap();
for (id, game) in games.iter_mut() {
if game.add_player(username) {
return Some(id.clone());
}
}
}
None
}
fn ban_player(&self, username: &str) {
{
let mut games = self.games.lock().unwrap();
games
.iter_mut()
.filter(|(_id, g)| g.has_player(username))
.for_each(|(_id, g)| g.remove_player(username));
}
{
let mut players = self.players.lock().unwrap();
players.remove(username);
}
}
更に改良するには、Player の操作をヘルパメソッド add_player() と remove_player() に カプセル化 してスコープを閉じるのを忘れないようにすれば良い
① の問題点: 競合状態 という別の問題が発生する
table:_
スレッド① スレッド②
add_and_join("Alice") に入り、Alice を players に追加
(このときに、players のロックを解除)
ban_player("Alice") に入り、Alice をすべての games から削除
(このときに、games のロックを解除)
Alice を players から削除する
(スレッド①はロックを解除しているためブロックされない)
Alice を実行中のゲームに追加
解決策②: 1 つの 同期プリミティブ で両方の状態をカバーする
∵ この問題は、互いに同期している必要がある 2 つのデータ構造があることであるので
code:rs
struct GameState {
players: HashMap<String, Player>,
games: HashMap<GameId, Game>,
}
struct GameServer {
state: Mutex<GameState>,
}
#Rust #Effective_Rust_―_Rustコードを改善し、エコシステムを最大限に活用するための35項目